package br.ufpe.cin.mac.util;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import br.ufpe.cin.mac.middleware.Middleware;
import br.ufpe.cin.mac.middleware.exceptions.InvokeException;

/**
 * Class Proxy
 * 
 * @author Rafael Roque
 * @author Osmany
 * 
 */
@SuppressWarnings("unused")
public class Proxy implements InvocationHandler {

	private Class receiver, sender;

	private Object owner;

	private long invoke;

	private Middleware mid;

	private Map<Integer, Message> buffer;

	public Proxy(Object owner, Object receiver, Middleware mid) {
		this.owner = owner;
		this.receiver = (Class) receiver;
		this.mid = mid;
		buffer = Collections.synchronizedMap(new HashMap<Integer, Message>());
		String ow = owner.getClass().getName();
		if (!ow.equals("app.RTPServerTest")) {
			new Thread() {
				public void run() {
					listen();
				}
			}.start();
		}
	}

	public Proxy(Object owner, Middleware mid) {
		this(owner, null, mid);
	}

	public Object invoke(final Object proxy, final Method meth,
			final Object[] args) throws Throwable {

		try {
			Message msg = prepareMessage(receiver.getSimpleName(), owner
					.getClass().getSimpleName(), meth, args, Constants.REQUEST);

			mid.send(msg, Constants.REQUEST);

			while (!hasMsg(msg.getMsgId())) {
				Thread.sleep(50);
			}
			msg = buffer.get(msg.getMsgId());

			return msg.getParameter()[0];
		} catch (Exception e) {
			return null;
		}
	}

	private Message prepareMessage(String receiver, String sender, Method meth,
			Object[] args, String type) {
		Message msg = new Message(sender, receiver, "Ok");

		Class<?>[] methParam;
		methParam = meth.getParameterTypes();
		String[] methSignature = new String[methParam.length];
		for (int i = 0; i < methParam.length; i++) {
			methSignature[i] = methParam[i].getName();
		}

		msg.setMethod(meth.getName());
		if (args[0] == null) {
			System.out.println("Problemas");
		}
		msg.setParameter(args);
		msg.setType(type);
		msg.setSignature(methSignature);

		return msg;
	}

	/**
	 * Metodo que fica lendo do middlware as mensagens disponiveis
	 */
	private void listen() {
		try {
			while (true) {
				List<Message> list = mid.receive(owner.getClass()
						.getSimpleName());
				Iterator<Message> it = list.iterator();

				while (it.hasNext()) {

					final Message msg = it.next();
					if (msg.getType().equals(Constants.REQUEST)) {
						new Thread() {
							public void run() {
								invokeApp(msg);
							}
						}.start();

					} else if (msg.getType().equals(Constants.RESPONSE)) {
						synchronized (buffer) {
							buffer.put(msg.getMsgId(), msg);
							System.out.println("Received: " + msg.getMsgId());
						}
					}
				}
				Thread.sleep(100);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void invokeApp(Message msg) {
		try {
			String msgMethog = msg.getMethod();
			Class[] paramsSig = msg.getSignatureClasses();
			Method meth = owner.getClass().getMethod(msgMethog, paramsSig);
			Object[] args = msg.getParameter();
			if (args == null) {
				args = new Object[paramsSig.length];
			}
			Object ret = meth.invoke(owner, args);
			msg.setParameter(new Object[] { ret });

			sendMessage(msg, Constants.RESPONSE);
		} catch (Exception e) {
			// TODO modificar esse catch
			e.printStackTrace();
		}
	}

	private void sendMessage(Message msg, String type) throws InvokeException {
		msg.setType(type);

		int index = msg.getInterceptors().size()-1;
		msg.getInterceptors().remove(index);
		msg.getOperators().remove(index);
		msg.getOperators().remove(index-1);
		List<String> ops = new LinkedList<String>();
		ops.add(Constants.OP_RESPONSE);
		msg.getOperators().add(index-1,ops);
		//	msg.addInterceptor(msg.getInterceptors().get(index-1), ops);
		String swap = msg.getSender();
		//System.err.println();
		msg.setSender(msg.getReceiver());
		msg.setReceiver(swap);

		mid.send(msg, type);
	}

	public boolean hasMsg(int msgID) {
		return buffer.containsKey(msgID);
	}

}
